package wili.logic;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Predicate;

import wili.c.Ca;
import wili.c.Misc9a;
import wili.c.Str9a;
import wili.exp.simple.Sexp;

/**
 *
 */
class AndOrNode {
	public final int id = ++GenID;
	public final AndOrExp exp;
	public Boolean isAnd, isYes = true;
	String op = "";
	final int start, end;
	public final String expSt;
	public Sexp sexp;
	AndOrNode dad;
	public List<AndOrNode> sons;

	public void replaceBy(AndOrNode nod) {
		Ca.asert(this != nod, "");
		if (this.dad == null) {
			exp.setRootNode(nod);
		} else {
			int ind = dad.sons.indexOf(this);
			dad.sons.set(ind, nod);
		}
		this.sons = null;
	}

	public void removeTrueFalseNode() {
		Ca.asert(false, "see AndOrNode2.java");
	}

	public String toString() {
		return start + ":" + this.getString(1);
	}

	private String getSt;

	public String getString(int refresh) {
		if (refresh != 1 && getSt != null)
			return getSt;
		if (this.sons == null) {
			if (sexp != null)
				return sexp.toString();
			return null;
		}
		StringBuilder sb = new StringBuilder();
		if (!this.isAnd || !isYes) {
			if (!isYes)
				sb.append("!");
			sb.append("(");
		}
		for (int i = 0; i < sons.size(); i++) {
			if (i > 0)
				sb.append(" " + op + " ");
			AndOrNode son = sons.get(i);
			String st = son.getString(refresh);
			sb.append(st);
		}
		if (!this.isAnd || !isYes) {
			sb.append(")");
		}
		return getSt = sb.toString();
	}

	AndOrNode(ToIndentLogicNode node, AndOrExp root) {
		Ca.asert(false, "10-07");
		this.isAnd = node.isAnd;
		this.isYes = node.isYes;
		this.exp = root;
		this.start = node.start;
		this.end = node.end;
		this.expSt = root.expSt.substring(start, end);
		if (node.sons == null) {
			this.sexp = Sexp.newInst(expSt);
			// Ca.log(++inc + ", MatchExp1=" + mathExp.toString() + ", " + expSt);
		} else {
			for (int i = 0; i < node.sons.size(); i++) {
				ToIndentLogicNode son = node.sons.get(i);
				AndOrNode son2 = new AndOrNode(son, root);
				this.addSon(son2);
			}
		}
	}

	AndOrNode(String expSt, int start, int end, AndOrExp cont) {
		/*
		 * 复合命题是由若干算符连接起来的若干命题，命题是括号或叶，括号内是复合命题或叶。 依次 splitBy or, and, mathOp, 跳过括号范围。
		 * 递归地为括号生成结点。 结点可能是算术运算符，当递归深入其左右数
		 */
		this.exp = cont;
		end = Str9a.lastIndexOfNotSpace(expSt, start, end) + 1;// 略过最右端的空格
		Boolean isAnd2 = null, isYes2 = true;
		String op2 = null;
		final String rootSt = expSt;
		String sp = Str9a.readSpaces(rootSt, start);
		start += sp.length();
		{// 起于左括，止于对应的右括
			char ch = rootSt.charAt(start);
			int start2 = start;
			if (ch == '!' && rootSt.charAt(start + 1) == '(') {
				isYes2 = false;
				start2 = start + 1;
			}
			if (rootSt.charAt(start2) == '(') {
				if (cont.lrIndMap.get(start2) == end - 1) {
					start = start2 + 1;
					end -= 1;
				}
			}
			this.start = start;
			this.end = end;
		}
		this.expSt = rootSt.substring(start, end).trim();
		if (!this.expSt.contains(cont.andOp) && !this.expSt.contains(cont.orOp)) {
			this.isAnd = this.isYes = null;
			this.op = null;
			String st = expSt.substring(start, end);
			if (cont.useSexp) {
				this.sexp = Sexp.newInst(st);
			} else {
				//this.mathExp = MathExpB1.newInst(st);
			}
			// Ca.log(++inc + ", MatchExp2=" + mathExp.toString() + ", " + expSt);
			return;
		}
		//this.mathExp = null;
		Ca.log(-1, "expSt:" + expSt);
		int from = start;
		String[] andOrOp = { cont.orOp, cont.andOp };
		for (int k = 0; k < andOrOp.length; k++) {
			String logicOp = andOrOp[k];
			for (int i = start; i < end; i++) {
				char ch = rootSt.charAt(i);
				if (i == 410)
					"".isEmpty();
				Ca.log(-1, i, "#", rootSt.substring(i));
				switch (ch) {
				case '\'':
				case '"': {
					String qt = Str9a.readQuote(rootSt, i);
					i += qt.length() - 1;
					continue;
				}
				case '(': {
					int rInd = cont.lrIndMap.get(i);
					i = rInd;
					continue;
				}
				default: {
					if (rootSt.startsWith("|{ObjType} eq 22", i))
						Coms21.pause();
					if (rootSt.startsWith(logicOp, i)) { // 生成逻辑逻算符左侧的结点。
						op2 = logicOp;
						isAnd2 = k == 1;
						String st = rootSt.substring(from, i).trim();
						if (st.startsWith("false "))
							Coms21.pause();
						if (st.isEmpty()) {
							throw new RuntimeException("Invalid expr: " + rootSt.substring(start));
						} else {
							this.addSon(new AndOrNode(expSt, from, i, cont));// 生成算符左侧结点
						}
						from = i + logicOp.length();
						i = from - 1;
					}
				}
				}
			}
			if (sons == null) {
				Ca.pause();
			} else {// 有子，必是或式|与式
				Ca.asert(op2 != null, "");
				if (from < end) {
					this.addSon(new AndOrNode(expSt, from, end, cont));// 生成算符右侧结点。
				} else {
					throw new RuntimeException("Invalid expr: " + rootSt.substring(start, end));
				}
				break;
			}
		}
		this.isAnd = isAnd2;
		this.isYes = isYes2;
		this.op = op2;
	}

	private void addSon(AndOrNode son) {
		if (sons == null)
			sons = new ArrayList<>();
		sons.add(son);
		son.dad = this;
	}

	@SuppressWarnings("rawtypes")
	public Boolean isTrue(Map cont) {
		if (isAnd == null) {
			if (exp.useSexp) {
				return this.sexp.isTrue(cont);
			}
			return null;
			//return this.mathExp.isTrue();
		} else if (isAnd) {
			for (AndOrNode son : sons) {
				if (!son.isTrue(cont)) {
					return false;
				}
			}
			return true;
		} else {
			for (AndOrNode son : sons) {
				if (son.isTrue(cont)) {
					return true;
				}
			}
			return false;
		}
	}

	public void print(String dent) {
		if (sons == null) {
			Ca.log(dent + this.expSt);
			return;
		}
		if (this.isAnd) {
			Ca.log(dent + "and");
		} else {
			Ca.log(dent + "or");
		}
		dent += "\t";
		for (int i = 0; i < sons.size(); i++) {
			AndOrNode son = sons.get(i);
			son.print(dent);
		}
	}

	public void traverse(Predicate<AndOrNode> pred) {
		pred.test(this);
		if (sons != null) {
			for (AndOrNode son : sons)
				son.traverse(pred);
		}
	}

	public static Map<String, List<AndOrNode>> groupList(List<AndOrNode> list, Function<AndOrNode, String> grpFun) {
		Map<String, List<AndOrNode>> ret = new HashMap<>();
		for (AndOrNode ele : list) {
			String grp = grpFun.apply(ele);
			if (grp != null)
				Misc9a.appendValueToListMap(grp, ele, ret, -1);
		}
		return ret;
	}

	public static void test() {
		AndOrExp_test.test(1);
	}

	public static int indexOfExpNode(AndOrNode nod, List<AndOrNode> list) {
		if (list == null)
			return -1;
		nod.standarlize();
		for (int i = 0; i < list.size(); i++) {
			AndOrNode son = list.get(i);
			son.standarlize();
			son.getString(1);
			if (nod.equals(son))
				return i;
		}
		Ca.pause();
		return -1;
	}

	public void standarlize() {
		if (sons == null)
			return;
		Ca.asert(!this.op.isEmpty(), op);
		for (int i = 0; i < sons.size(); i++) {
			AndOrNode son = sons.get(i);
			son.standarlize();
			if (this.op.equals(son.op)) {
				sons.remove(i);
				for (AndOrNode ton : son.sons)
					ton.dad = this;
				sons.addAll(i, son.sons);
			}
		}
	}

	public boolean equals(Object obj) {
		if (obj instanceof AndOrNode) {
			AndOrNode nod = (AndOrNode) obj;
			if (!Objects.equals(this.isAnd, nod.isAnd))
				return false;
			if (!Objects.equals(this.isYes, nod.isYes))
				return false;
			if ((this.sons == null) != (nod.sons == null))
				return false;
			if (sons == null) {
				return this.sexp.equals(nod.sexp);
			} else {
				if (sons.size() != nod.sons.size())
					return false;
				for (int i = 0; i < sons.size(); i++) {
					AndOrNode son = sons.get(i);
					AndOrNode son2 = nod.sons.get(i);
					if (!son.equals(son2))
						return false;
				}
				return true;
			}
		}
		return false;
	}

	public void sortSonsBySt(int depth) {// no recursion.
		if (sons == null || --depth < 1)
			return;
		for (AndOrNode son : sons) {
			son.sortSonsBySt(depth);
		}
		Collections.sort(sons, new Comparator<AndOrNode>() {
			@Override
			public int compare(AndOrNode o1, AndOrNode o2) {
				String s1 = o1.expSt, s2 = o2.expSt;
				if (Boolean.FALSE && s1.length() != s2.length())
					return s1.length() - s2.length();
				return s1.compareTo(s2);
			}
		});
	}

	public static void main(String[] args) {
		test();
	}

	static int GenID, inc;
}
